<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.0.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/icon.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/ficon.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/icon.png">
  <link rel="mask-icon" href="/images/icon.png" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib/pace/pace-theme-minimal.min.css">
  <script src="/lib/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"bqlin.github.io","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":true,"comments":{"style":"tabs","active":"gitalk","storage":true,"lazyload":false,"nav":{"gitalk":{"order":-2}},"activeClass":"gitalk"},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":-1,"unescape":false,"preload":false},"motion":{"enable":false,"async":true,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="当你使用音频队列服务播放音频时，源几乎可以是任意的——磁盘文件、基于软件音频合成器、内存中的对象等。本章介绍最常见的情况：播放磁盘上的文件。">
<meta property="og:type" content="article">
<meta property="og:title" content="Audio Queue Services Programming Guide：播放音频">
<meta property="og:url" content="https://bqlin.github.io/posts/audio_queue_services_pg_playing_audio/index.html">
<meta property="og:site_name" content="權咚领域">
<meta property="og:description" content="当你使用音频队列服务播放音频时，源几乎可以是任意的——磁盘文件、基于软件音频合成器、内存中的对象等。本章介绍最常见的情况：播放磁盘上的文件。">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-09-27T04:18:11.000Z">
<meta property="article:modified_time" content="2021-09-27T04:18:11.000Z">
<meta property="article:author" content="權咚">
<meta property="article:tag" content="Apple">
<meta property="article:tag" content="Audio Queue Services Programming Guide">
<meta property="article:tag" content="音视频">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="https://bqlin.github.io/posts/audio_queue_services_pg_playing_audio/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>Audio Queue Services Programming Guide：播放音频 | 權咚领域</title>
  
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-105338942-1"></script>
    <script>
      if (CONFIG.hostname === location.hostname) {
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', 'UA-105338942-1');
      }
    </script>


  <script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?98fcf61fd269408d07dcbc1e49311f9a";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>




  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="權咚领域" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">權咚领域</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">Making app a work of art.</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://bqlin.github.io/posts/audio_queue_services_pg_playing_audio/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/icon.png">
      <meta itemprop="name" content="權咚">
      <meta itemprop="description" content="🤘🤯👾">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="權咚领域">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Audio Queue Services Programming Guide：播放音频
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2021-09-27 12:18:11" itemprop="dateCreated datePublished" datetime="2021-09-27T12:18:11+08:00">2021-09-27</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E7%BF%BB%E8%AF%91/" itemprop="url" rel="index"><span itemprop="name">翻译</span></a>
                </span>
            </span>

          
            <span class="post-meta-item" title="阅读次数" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span><br>
            <span class="post-meta-item" title="本文字数">
              <span class="post-meta-item-icon">
                <i class="far fa-file-word"></i>
              </span>
                <span class="post-meta-item-text">本文字数：</span>
              <span>14k</span>
            </span>
            <span class="post-meta-item" title="阅读时长">
              <span class="post-meta-item-icon">
                <i class="far fa-clock"></i>
              </span>
                <span class="post-meta-item-text">阅读时长 &asymp;</span>
              <span>13 分钟</span>
            </span>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>当你使用音频队列服务播放音频时，源几乎可以是任意的——磁盘文件、基于软件音频合成器、内存中的对象等。本章介绍最常见的情况：播放磁盘上的文件。</p>
<span id="more"></span>
<blockquote>
<p><strong>注意：</strong>本章介绍了基于ANSI-C的播放实现，并使用了Mac OS X Core Audio SDK的C++类。有关Objective-C的示例，参阅<a target="_blank" rel="noopener" href="http://developer.apple.com/devcenter/ios/">iOS Dev Center</a>中的_SpeakHere_示例代码。</p>
</blockquote>
<p>要把播放功能添加到程序中，通常需要执行以下步骤：</p>
<ol type="1">
<li>定义一个自定义结构体来管理状态、格式和路径信息。</li>
<li>编写音频队列回调函数来执行实际的播放。</li>
<li>编写代码以确定音频队列缓冲区的合适大小。</li>
<li>打开音频文件进行播放，然后确定其音频数据格式。</li>
<li>创建一个播放音频队列并进行相关配置。</li>
<li>分配和排队音频队列缓冲区。告诉音频队列开始播放。完成后，播放回调函数告诉音频队列停止。</li>
<li>处理音频队列，释放资源。</li>
</ol>
<p>本章的剩余部分详细介绍了每个步骤。</p>
<h2 id="定义结构体管理状态">定义结构体管理状态</h2>
<p>首先，定义一个结构体，将用它来管理音频格式和音频队列状态信息，如清单3-1所示：</p>
<p><strong>清单3-1</strong> 播放音频队列的自定义结构体</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> kNumberBuffers = <span class="number">3</span>;                              <span class="comment">// 1</span></span><br><span class="line"><span class="keyword">struct</span> AQPlayerState &#123;</span><br><span class="line">    AudioStreamBasicDescription   mDataFormat;                    <span class="comment">// 2</span></span><br><span class="line">    AudioQueueRef                 mQueue;                         <span class="comment">// 3</span></span><br><span class="line">    AudioQueueBufferRef           mBuffers[kNumberBuffers];       <span class="comment">// 4</span></span><br><span class="line">    AudioFileID                   mAudioFile;                     <span class="comment">// 5</span></span><br><span class="line">    <span class="built_in">UInt32</span>                        bufferByteSize;                 <span class="comment">// 6</span></span><br><span class="line">    SInt64                        mCurrentPacket;                 <span class="comment">// 7</span></span><br><span class="line">    <span class="built_in">UInt32</span>                        mNumPacketsToRead;              <span class="comment">// 8</span></span><br><span class="line">    AudioStreamPacketDescription  *mPacketDescs;                  <span class="comment">// 9</span></span><br><span class="line">    <span class="keyword">bool</span>                          mIsRunning;                     <span class="comment">// 10</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>结构体中大多数字段与用于录制的自定义结构体几乎相同，如<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW15">Define a Custom Structure to Manage State</a>所述。例如，<code>mDataFormat</code>字段保存正在播放的文件格式。录制时，类似的字段保存了写入磁盘的文件格式。</p>
<p>以下是该结构体各字段介绍：</p>
<ol type="1">
<li>设置要使用的音频队列缓冲区数量。如<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW13">Audio Queue Buffers</a>所述，3个通常是不错的选择。</li>
<li><code>AudioStreamBasicDescription</code>结构体（来自<code>CoreAudioTypes.h</code>）表示正在播放的文件的音频数据格式。该格式由<code>mQueue</code>字段指定的音频队列使用。 <code>mDataFormat</code>字段通过查询音频文件的<code>kAudioFilePropertyDataFormat</code>属性来填充该字段，如<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW25">Obtaining a File’s Audio Data Format</a>所述。 有关<code>AudioStreamBasicDescription</code>结构体的详细信息，参阅_<a target="_blank" rel="noopener" href="https://developer.apple.com/documentation/coreaudio/core_audio_data_types">Core Audio Data Types Reference</a>_。</li>
<li>程序创建的播放音频队列。</li>
<li>一个数组，包含指向音频队列管理的音频队列缓冲区的指针。</li>
<li>代表程序播放的音频文件的对象。</li>
<li>每个音频队列缓冲区的大小（以字节为单位）。该值在音频队列创建之后和开始之前，由<code>DeriveBufferSize</code>函数计算。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW23">Write a Function to Derive Playback Audio Queue Buffer Size</a>。</li>
<li>音频文件中下一个要播放的数据包索引。</li>
<li>每次调用音频队列的播放回调函数时，要读取的数据包数量。就像<code>bufferByteSize</code>字段一样，在音频队列创建之后和开始之前，由<code>DeriveBufferSize</code>函数计算该值。</li>
<li>对于VBR音频数据，该字段是正在播放的文件的数据包描述数组。对于CBR数据，该字段为<code>NULL</code>。</li>
<li>一个布尔值，表示音频队列是否正在运行。</li>
</ol>
<h2 id="编写播放音频队列回调函数">编写播放音频队列回调函数</h2>
<p>下面，编写一个播放音频队列回调函数。该回调函数执行三项主要任务：</p>
<ul>
<li>从音频文件中读取指定数量的数据，并将其放入音频队列缓冲区中。</li>
<li>把音频队列缓冲区排队到缓冲区队列中。</li>
<li>当没有更多数据要从音频文件中读取时，告诉音频队列停止。</li>
</ul>
<p>本节展示来一个回调声明示例，分别描述各个任务，最后给出完整的播放回调函数。有关播放回调函数的作用，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW3">图1-4</a>。</p>
<h3 id="播放音频队列回调声明">播放音频队列回调声明</h3>
<p>清单3-2展示了一个播放音频回调函数的示例声明，<code>AudioQueueOutputCallback</code>在<code>AudioQueue.h</code>声明为：</p>
<p><strong>清单3-2</strong> 播放音频队列回调声明</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> HandleOutputBuffer (</span><br><span class="line">    <span class="keyword">void</span>                 *aqData,                 <span class="comment">// 1</span></span><br><span class="line">    AudioQueueRef        inAQ,                    <span class="comment">// 2</span></span><br><span class="line">    AudioQueueBufferRef  inBuffer                 <span class="comment">// 3</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>通常，<code>aqData</code>是包含定义音频队列状态信息的自定义结构体。如<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW15">Define a Custom Structure to Manage State</a>所述。</li>
<li>持有该回调函数的音频队列。</li>
<li>音频队列缓冲区，回调函数通过从音频文件中读取，来填充数据。</li>
</ol>
<h3 id="从文件读取到音频队列缓冲区">从文件读取到音频队列缓冲区</h3>
<p>播放音频队列回调函数的第一个操作是从音频文件中读取数据并将其放在音频队列缓冲区中，如清单3-3所示。</p>
<p><strong>清单3-3</strong> 从音频文件读取到音频队列缓冲区</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">AudioFileReadPackets (                        <span class="comment">// 1</span></span><br><span class="line">    pAqData-&gt;mAudioFile,                      <span class="comment">// 2</span></span><br><span class="line">    <span class="literal">false</span>,                                    <span class="comment">// 3</span></span><br><span class="line">    &amp;numBytesReadFromFile,                    <span class="comment">// 4</span></span><br><span class="line">    pAqData-&gt;mPacketDescs,                    <span class="comment">// 5</span></span><br><span class="line">    pAqData-&gt;mCurrentPacket,                  <span class="comment">// 6</span></span><br><span class="line">    &amp;numPackets,                              <span class="comment">// 7</span></span><br><span class="line">    inBuffer-&gt;mAudioData                      <span class="comment">// 8</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioFileReadPackets</code>函数（在<code>AudioFile.h</code>中声明），从音频文件读取数据并将其放入缓冲区中。</li>
<li>要读取的音频文件。</li>
<li>用<code>false</code>表示该函数在读取时不应缓存数据。</li>
<li>输出时，是从音频文件读取的音频数据字节数。</li>
<li>输出时，是从音频文件中读取的数据包描述数组。对于CBR数据，该参数输入<code>NULL</code>。</li>
<li>从音频文件中读取第一个数据包的索引。</li>
<li>输入时，是要从音频文件读取的数据包数量。输出时，是实际读取的包数量。</li>
<li>在输出时，填充的音频队列缓冲区包含从音频文件读取的数据。</li>
</ol>
<h3 id="排队音频队列缓冲区">排队音频队列缓冲区</h3>
<p>现在已经从音频文件中读取数据并将其放在音频队列缓冲区中，回调函数让缓冲区入队，如清单3-4所示。进入缓冲区队列后，缓冲区的音频数据可用于音频队列发送到输出设备。</p>
<p><strong>清单3-4</strong> 从磁盘中读取后排队音频队列缓冲区</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">AudioQueueEnqueueBuffer (                      <span class="comment">// 1</span></span><br><span class="line">    pAqData-&gt;mQueue,                           <span class="comment">// 2</span></span><br><span class="line">    inBuffer,                                  <span class="comment">// 3</span></span><br><span class="line">    (pAqData-&gt;mPacketDescs ? numPackets : <span class="number">0</span>),  <span class="comment">// 4</span></span><br><span class="line">    pAqData-&gt;mPacketDescs                      <span class="comment">// 5</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioQueueEnqueueBuffer</code>函数把音频队列缓冲区添加到缓冲区队列。</li>
<li>持有缓冲区队列的音频队列。</li>
<li>要排队的音频队列缓冲区。</li>
<li>音频队列缓冲区数据中的数据包数量。对于不使用数据包描述的CBR数据，设为<code>0</code>。</li>
<li>对于使用数据描述的压缩音频数据格式，数据包描述在缓冲区中。</li>
</ol>
<h3 id="停止音频队列">停止音频队列</h3>
<p>回调函数最后一个操作是检查是否有更多的数据，要从正在播放的音频文件中读取。在发现文件结尾后，回调函数告诉音频队列停止，如清单3-5所示。</p>
<p><strong>清单3-5</strong>  Stopping an audio queue</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (numPackets == <span class="number">0</span>) &#123;                          <span class="comment">// 1</span></span><br><span class="line">    AudioQueueStop (                            <span class="comment">// 2</span></span><br><span class="line">        pAqData-&gt;mQueue,                        <span class="comment">// 3</span></span><br><span class="line">        <span class="literal">false</span>                                   <span class="comment">// 4</span></span><br><span class="line">    );</span><br><span class="line">    pAqData-&gt;mIsRunning = <span class="literal">false</span>;                <span class="comment">// 5</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>检查<code>AudioFileReadPackets</code>函数（由之前的回调函数调用）读取的数据包数量是否为<code>0</code>。</li>
<li><code>AudioQueueStop</code>函数停止音频队列。</li>
<li>要停止的音频队列。</li>
<li>播放所有排队的缓冲区后，异步停止音频队列。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW17">Audio Queue Control and State</a>。</li>
<li>设置结构体标志，表示播放已完成。</li>
</ol>
<h3 id="完整播放音频队列回调函数">完整播放音频队列回调函数</h3>
<p>清单3-6展示了完整播放音频队列回调的基本代码。和本文档的其他示例代码一样，该清单代码不包含错误处理。</p>
<p><strong>清单3-6</strong> 一个播放音频队列回调函数</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> HandleOutputBuffer (</span><br><span class="line">    <span class="keyword">void</span>                *aqData,</span><br><span class="line">    AudioQueueRef       inAQ,</span><br><span class="line">    AudioQueueBufferRef inBuffer</span><br><span class="line">) &#123;</span><br><span class="line">    AQPlayerState *pAqData = (AQPlayerState *) aqData;        <span class="comment">// 1</span></span><br><span class="line">    <span class="keyword">if</span> (pAqData-&gt;mIsRunning == <span class="number">0</span>) <span class="keyword">return</span>;                     <span class="comment">// 2</span></span><br><span class="line">    <span class="built_in">UInt32</span> numBytesReadFromFile;                              <span class="comment">// 3</span></span><br><span class="line">    <span class="built_in">UInt32</span> numPackets = pAqData-&gt;mNumPacketsToRead;           <span class="comment">// 4</span></span><br><span class="line">    AudioFileReadPackets (</span><br><span class="line">        pAqData-&gt;mAudioFile,</span><br><span class="line">        <span class="literal">false</span>,</span><br><span class="line">        &amp;numBytesReadFromFile,</span><br><span class="line">        pAqData-&gt;mPacketDescs, </span><br><span class="line">        pAqData-&gt;mCurrentPacket,</span><br><span class="line">        &amp;numPackets,</span><br><span class="line">        inBuffer-&gt;mAudioData </span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">if</span> (numPackets &gt; <span class="number">0</span>) &#123;                                     <span class="comment">// 5</span></span><br><span class="line">        inBuffer-&gt;mAudioDataByteSize = numBytesReadFromFile;  <span class="comment">// 6</span></span><br><span class="line">       AudioQueueEnqueueBuffer ( </span><br><span class="line">            pAqData-&gt;mQueue,</span><br><span class="line">            inBuffer,</span><br><span class="line">            (pAqData-&gt;mPacketDescs ? numPackets : <span class="number">0</span>),</span><br><span class="line">            pAqData-&gt;mPacketDescs</span><br><span class="line">        );</span><br><span class="line">        pAqData-&gt;mCurrentPacket += numPackets;                <span class="comment">// 7 </span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        AudioQueueStop (</span><br><span class="line">            pAqData-&gt;mQueue,</span><br><span class="line">            <span class="literal">false</span></span><br><span class="line">        );</span><br><span class="line">        pAqData-&gt;mIsRunning = <span class="literal">false</span>; </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>实例化后提供给音频队列的自定义结构体，包含要播放的音频文件对象（类型为<code>AudioFileID</code>），以及各种状态数据。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW15">Define a Custom Structure to Manage State</a>。</li>
<li>如果音频队列已停止，则立即返回。</li>
<li>一个变量，用于保存从正在播放的文件中读取的音频数据字节数。</li>
<li>使用要从正播放的文件中读取的数据包来初始化<code>numPackets</code>变量。</li>
<li>测试是否从文件中检索了一些音频数据。如果是，则让新填充的缓冲区入队；否则停止音频队列。</li>
<li>告诉音频队列缓冲区结构体已读取数据的字节数。</li>
<li>根据读取的数据包数量增加数据包索引。</li>
</ol>
<h2 id="编写函数计算播放音频队列缓冲区大小">编写函数计算播放音频队列缓冲区大小</h2>
<p>音频队列服务希望你的程序为使用的音频队列缓冲区指定大小，如清单3-7所示。它得出的缓冲区大小足以容纳给定的音频时长。</p>
<p>创建播放音频队列后，你将在程序中调用<code>DeriveBufferSize</code>函数，作为后续音频队列分配缓冲区的先决条件。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW14">Write a Function to Derive Recording Audio Queue Buffer Size</a>。为了播放，还需要：</p>
<ul>
<li>在每次回调函数调用<code>AudioFileReadPackets</code>函数，得出要读取的数据包数量。</li>
<li>设置缓冲区大小的下限，以避免过多的磁盘访问。</li>
</ul>
<p>这里的计算考虑了从磁盘读取的音频数据格式。该格式包括了可能影响缓冲区大小的所有因素，例如音频通道数量。</p>
<p><strong>清单3-7</strong> 得出播放音频队列缓冲区大小</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> DeriveBufferSize (</span><br><span class="line">    AudioStreamBasicDescription &amp;ASBDesc,                            <span class="comment">// 1</span></span><br><span class="line">    <span class="built_in">UInt32</span>                      maxPacketSize,                       <span class="comment">// 2</span></span><br><span class="line">    Float64                     seconds,                             <span class="comment">// 3</span></span><br><span class="line">    <span class="built_in">UInt32</span>                      *outBufferSize,                      <span class="comment">// 4</span></span><br><span class="line">    <span class="built_in">UInt32</span>                      *outNumPacketsToRead                 <span class="comment">// 5</span></span><br><span class="line">) &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> maxBufferSize = <span class="number">0x50000</span>;                        <span class="comment">// 6</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> minBufferSize = <span class="number">0x4000</span>;                         <span class="comment">// 7</span></span><br><span class="line"> </span><br><span class="line">    <span class="keyword">if</span> (ASBDesc.mFramesPerPacket != <span class="number">0</span>) &#123;                             <span class="comment">// 8</span></span><br><span class="line">        Float64 numPacketsForTime =</span><br><span class="line">            ASBDesc.mSampleRate / ASBDesc.mFramesPerPacket * seconds;</span><br><span class="line">        *outBufferSize = numPacketsForTime * maxPacketSize;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;                                                         <span class="comment">// 9</span></span><br><span class="line">        *outBufferSize =</span><br><span class="line">            maxBufferSize &gt; maxPacketSize ?</span><br><span class="line">                maxBufferSize : maxPacketSize;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">if</span> (                                                             <span class="comment">// 10</span></span><br><span class="line">        *outBufferSize &gt; maxBufferSize &amp;&amp;</span><br><span class="line">        *outBufferSize &gt; maxPacketSize</span><br><span class="line">    )</span><br><span class="line">        *outBufferSize = maxBufferSize;</span><br><span class="line">    <span class="keyword">else</span> &#123;                                                           <span class="comment">// 11</span></span><br><span class="line">        <span class="keyword">if</span> (*outBufferSize &lt; minBufferSize)</span><br><span class="line">            *outBufferSize = minBufferSize;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    *outNumPacketsToRead = *outBufferSize / maxPacketSize;           <span class="comment">// 12</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>音频队列的<code>AudioStreamBasicDescription</code>结构体。</li>
<li>正在播放的音频文件中最大数据包的预估大小。你可以通过<code>kAudioFilePropertyPacketSizeUpperBound</code>属性ID，使用<code>AudioFileGetProperty</code>函数（在<code>AudioFile.h</code>中声明）得出该值。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW9">Set Sizes for a Playback Audio Queue</a>。</li>
<li>为每个音频缓冲区指定大小（以秒为单位）。</li>
<li>在输出时，是每个音频队列缓冲区的大小（以字节为单位）。</li>
<li>在输出时，是在每次播放音频队列回调时，从文件读取的音频数据包的数量。</li>
<li>音频队列缓冲区大小的上限（以字节为单位）。在该示例中，上限设为320 KB。这相等于以96 kHz采样率，大约持续5秒的24位立体声音频。</li>
<li>音频队列缓冲区大小的下限（以字节为单位）。在该示例中，下限设为16 KB。</li>
<li>对于定义每个数据包固定帧数的音频数据格式，需要得出音频队列缓冲区大小。</li>
<li>对于没定义每个数据包固定帧数的音频格式，需要根据最大数据包大小和设置的上限得出合理的音频队列缓冲区大小。</li>
<li>如果得出的缓冲区大小大于设置的上限，则考虑预估的最大数据包大小，并将其调整为边界值。</li>
<li>如果得出的缓冲区大小低于设置的下限，则将其调整为下限。</li>
<li>计算每次调用回调时从音频文件读取的数据包数量。</li>
</ol>
<h2 id="打开音频文件进行播放">打开音频文件进行播放</h2>
<p>现在，使用以下步骤打开音频文件进行播放：</p>
<ol type="1">
<li>获取一个表示要播放的音频文件的CFURL对象。</li>
<li>打开文件。</li>
<li>获取文件的音频数据格式。</li>
</ol>
<h3 id="获取音频文件的cfurl对象">获取音频文件的CFURL对象</h3>
<p>清单3-8展示了如何为要播放的音频文件获取CFURL对象。在下一步中使用CFURL对象，打开文件。</p>
<p><strong>清单3-8</strong> 获取音频文件的CFURL对象</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">CFURLRef</span> audioFileURL =</span><br><span class="line">    <span class="built_in">CFURLCreateFromFileSystemRepresentation</span> (           <span class="comment">// 1</span></span><br><span class="line">        <span class="literal">NULL</span>,                                           <span class="comment">// 2</span></span><br><span class="line">        (<span class="keyword">const</span> <span class="built_in">UInt8</span> *) filePath,                       <span class="comment">// 3</span></span><br><span class="line">        strlen (filePath),                              <span class="comment">// 4</span></span><br><span class="line">        <span class="literal">false</span>                                           <span class="comment">// 5</span></span><br><span class="line">    );</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>CFURLCreateFromFileSystemRepresentation</code>函数（在<code>CFURL.h</code>中声明），创建一个CFURL对象，该对象表示要播放的文件。</li>
<li>用<code>NULL</code>或<code>kCFAllocatorDefault</code>，表示使用当前默认的内存分配器。</li>
<li>想要转换为CFURL的文件系统路径。在生产代码中，通常会从用户获取<code>filePath</code>值。</li>
<li>文件系统路径中的字节数。</li>
<li><code>false</code>值表示<code>filePath</code>代表文件，而不是目录。</li>
</ol>
<h3 id="打开音频文件">打开音频文件</h3>
<p>清单3-9展示了如何打开音频文件进行播放。</p>
<p><strong>清单3-9</strong> 打开音频文件进行播放</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">AQPlayerState aqData;                                   <span class="comment">// 1</span></span><br><span class="line"> </span><br><span class="line">OSStatus result =</span><br><span class="line">    AudioFileOpenURL (                                  <span class="comment">// 2</span></span><br><span class="line">        audioFileURL,                                   <span class="comment">// 3</span></span><br><span class="line">        fsRdPerm,                                       <span class="comment">// 4</span></span><br><span class="line">        <span class="number">0</span>,                                              <span class="comment">// 5</span></span><br><span class="line">        &amp;aqData.mAudioFile                              <span class="comment">// 6</span></span><br><span class="line">    );</span><br><span class="line"> </span><br><span class="line"><span class="built_in">CFRelease</span> (audioFileURL);                               <span class="comment">// 7</span></span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>创建<code>AQPlayerState</code>自定义结构体实例（参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW15">Define a Custom Structure to Manage State</a>）。打开音频文件进行播放时，可以使用该实例存放音频文件对象（类型为<code>AudioFileID</code>）。</li>
<li><code>AudioFileOpenURL</code>函数（在<code>AudioFile.h</code>中声明），打开要播放的文件。</li>
<li>要播放文件的引用。</li>
<li>与正在播放文件一起使用的文件权限。可用权限在文件管理器的<code>File Access Permission Constants</code>枚举中定义。在该示例中，请求读取文件的权限。</li>
<li>可选文件类型hint。这里的<code>0</code>表示该示例未使用该功能。</li>
<li>在输出时，对音频文件的引用将放在自定义结构体的<code>mAudioFile</code>字段。</li>
<li>释放在第一步创建的CFURL对象。</li>
</ol>
<h3 id="获取文件的音频数据格式">获取文件的音频数据格式</h3>
<p>清单3-10展示了如何获取文件的音频数据格式。</p>
<p><strong>清单3-10</strong> 获取文件的音频数据格式</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UInt32</span> dataFormatSize = <span class="keyword">sizeof</span> (aqData.mDataFormat);    <span class="comment">// 1</span></span><br><span class="line"> </span><br><span class="line">AudioFileGetProperty (                                  <span class="comment">// 2</span></span><br><span class="line">    aqData.mAudioFile,                                  <span class="comment">// 3</span></span><br><span class="line">    kAudioFilePropertyDataFormat,                       <span class="comment">// 4</span></span><br><span class="line">    &amp;dataFormatSize,                                    <span class="comment">// 5</span></span><br><span class="line">    &amp;aqData.mDataFormat                                 <span class="comment">// 6</span></span><br><span class="line">);</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>获取在查询音频文件有关音频数据格式时要使用的预期属性值大小。</li>
<li><code>AudioFileGetProperty</code>函数（在<code>AudioFile.h</code>中声明），获取音频文件中指定属性的值。</li>
<li>音频文件对象（类型为<code>AudioFileID</code>），表示要获取其音频数据格式的文件。</li>
<li>用户获取音频文件的数据格式的属性ID。</li>
<li>输入时，是描述音频文件的数据格式的<code>AudioStreamBasicDescription</code>结构体的预期大小。输出时，是其实际大小。播放程序不需要使用该值。</li>
<li>在输出时，从音频文件获得<code>AudioStreamBasicDescription</code>结构体的完整音频数据格式。该行通过把文件的音频数据格式存储在音频队列的自定义结构体中，将其应用于音频队列。</li>
</ol>
<h2 id="创建播放音频队列">创建播放音频队列</h2>
<p>清单3-11展示了如何创建播放音频队列。注意，<code>AudioQueueNewOutput</code>函数使用了在之前步骤中配置的自定义结构体和回调函数，以及要播放文件的音频数据格式。</p>
<p><strong>清单3-11</strong> 创建播放音频队列</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">AudioQueueNewOutput (                                <span class="comment">// 1</span></span><br><span class="line">    &amp;aqData.mDataFormat,                             <span class="comment">// 2</span></span><br><span class="line">    HandleOutputBuffer,                              <span class="comment">// 3</span></span><br><span class="line">    &amp;aqData,                                         <span class="comment">// 4</span></span><br><span class="line">    <span class="built_in">CFRunLoopGetCurrent</span> (),                          <span class="comment">// 5</span></span><br><span class="line">    kCFRunLoopCommonModes,                           <span class="comment">// 6</span></span><br><span class="line">    <span class="number">0</span>,                                               <span class="comment">// 7</span></span><br><span class="line">    &amp;aqData.mQueue                                   <span class="comment">// 8</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioQueueNewOutput</code>函数创建一个新的播放音频队列。</li>
<li>设置要播放音频队列的音频数据格式。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW25">Obtaining a File’s Audio Data Format</a>。</li>
<li>和播放音频队列一起使用的回调函数。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW2">Write a Playback Audio Queue Callback</a>。</li>
<li>播放音频队列的自定义数据结构体。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW15">Define a Custom Structure to Manage State</a>。</li>
<li>当前的run loop，将在其调用音频队列回调函数。</li>
<li>run loop模式。通常设为<code>kCFRunLoopCommonModes</code>。</li>
<li>保留参数，必需为<code>0</code>。</li>
<li>在输出时，新分配的播放音频队列。</li>
</ol>
<h2 id="设置播放音频队列大小">设置播放音频队列大小</h2>
<p>接下来，设置播放音频队列的一些大小值。在为音频队列分配缓冲区时，以及开始读取音频文件之前，请使用这些大小值。</p>
<p>本节中的代码清单展示了如何设置：</p>
<ul>
<li>音频队列缓冲区大小。</li>
<li>每次调用播放音频队列回调函数时要读取的数据包数量。</li>
<li>数组大小，用于保存一个缓冲区的音频数据的数据包描述。</li>
</ul>
<h3 id="设置缓冲区大小和要读取的数据包数量">设置缓冲区大小和要读取的数据包数量</h3>
<p>清单3-12展示了如何使用之前编写的<code>DeriveBufferSize</code>函数（参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW23">Write a Function to Derive Playback Audio Queue Buffer Size</a>）。这里的目的是为每个音频队列缓冲区设置一个大小（以字节为单位），并确定每次调用播放音频队列回调函数时要读取的包数量。</p>
<p>该代码使用最大数据包大小的保守预估值，Core Audio通过<code>kAudioFilePropertyPacketSizeUpperBound</code>属性提供了该预估值。在大多数情况下，比起花时间读取整个音频文件以获得实际的最大数据包大小，使用这种近似（但快速）的技术更好。</p>
<p><strong>清单3-12</strong> 设置播放音频队列缓冲区的大小和要读取的数据包数量</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UInt32</span> maxPacketSize;</span><br><span class="line"><span class="built_in">UInt32</span> propertySize = <span class="keyword">sizeof</span> (maxPacketSize);</span><br><span class="line">AudioFileGetProperty (                               <span class="comment">// 1</span></span><br><span class="line">    aqData.mAudioFile,                               <span class="comment">// 2</span></span><br><span class="line">    kAudioFilePropertyPacketSizeUpperBound,          <span class="comment">// 3</span></span><br><span class="line">    &amp;propertySize,                                   <span class="comment">// 4</span></span><br><span class="line">    &amp;maxPacketSize                                   <span class="comment">// 5</span></span><br><span class="line">);</span><br><span class="line"> </span><br><span class="line">DeriveBufferSize (                                   <span class="comment">// 6</span></span><br><span class="line">    aqData.mDataFormat,                              <span class="comment">// 7</span></span><br><span class="line">    maxPacketSize,                                   <span class="comment">// 8</span></span><br><span class="line">    <span class="number">0.5</span>,                                             <span class="comment">// 9</span></span><br><span class="line">    &amp;aqData.bufferByteSize,                          <span class="comment">// 10</span></span><br><span class="line">    &amp;aqData.mNumPacketsToRead                        <span class="comment">// 11</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioFileGetProperty</code>函数（在<code>AudioFile.h</code>中声明），获取音频文件的指定属性的值。这里，可以用它来获取要播放文件中音频数据包大小的保守上限值（以字节为单位）。</li>
<li>要播放的音频文件对象（类型为<code>AudioFileID</code>）。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW24">Opening an Audio File</a>。</li>
<li>用于获取音频文件中数据包大小的保守上限的属性ID。</li>
<li>输出时，<code>kAudioFilePropertyPacketSizeUpperBound</code>属性的大小（以字节为单位）。</li>
<li>输出时，要播放的文件的数据包大小的保守上限（以字节为单位）。</li>
<li><code>DeriveBufferSize</code>函数（在<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW23">Write a Function to Derive Playback Audio Queue Buffer Size</a>中描述），设置来缓冲区大小和每次调用回调函数时要读取的数据包数量。</li>
<li>要播放的文件的音频数据格式。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW25">Obtaining a File’s Audio Data Format</a>。</li>
<li>来自第5行的音频文件最大数据包大小的预估值。</li>
<li>每次音频队列缓冲区应保留的音频时长（以秒为单位）。此处设置半秒是个不错的选择。</li>
<li>在输出时，每个音频队列缓冲区大小（以字节为单位）。该值放在音频队列的自定义结构体中。</li>
<li>在输出时，是在每次播放音频队列回调时要读取的数据包数量。该值也放在音频队列的自定义结构体中。</li>
</ol>
<h3 id="给数据包描述数组分配内存">给数据包描述数组分配内存</h3>
<p>现在，给数组分配内存，以保存一个缓冲区的音频数据的数据包描述。CBR数据不使用数据包描述，因此CBR的情况（清单3-13中的步骤3）非常简单。</p>
<p><strong>清单3-13</strong> 给数据包描述数组分配内存</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">bool</span> isFormatVBR = (                                       <span class="comment">// 1</span></span><br><span class="line">    aqData.mDataFormat.mBytesPerPacket == <span class="number">0</span> ||</span><br><span class="line">    aqData.mDataFormat.mFramesPerPacket == <span class="number">0</span></span><br><span class="line">);</span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> (isFormatVBR) &#123;                                         <span class="comment">// 2</span></span><br><span class="line">    aqData.mPacketDescs =</span><br><span class="line">      (AudioStreamPacketDescription*) malloc (</span><br><span class="line">        aqData.mNumPacketsToRead * <span class="keyword">sizeof</span> (AudioStreamPacketDescription)</span><br><span class="line">      );</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;                                                   <span class="comment">// 3</span></span><br><span class="line">    aqData.mPacketDescs = <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>确定音频文件的数据格式是VBR还是CBR。在VBR数据中，bytes-per-packet或frames-per-packet值的一个或两个是可变的，因此列出在音频队列的<code>AudioStreamBasicDescription</code>结构体中这两个值为<code>0</code>的情况。</li>
<li>对于包含VBR数据的音频文件，则为数据包描述数组分配内存。根据每次播放回调调用时要读取的音频数据包数量，计算所需内存。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW26">Setting Buffer Size and Number of Packets to Read</a>。</li>
<li>对于包含CBR数据的音频文件（例如线性PCM），音频队列不使用数据包描述数组。</li>
</ol>
<h2 id="给播放音频队列设置magic-cookie">给播放音频队列设置Magic Cookie</h2>
<p>某些压缩音频格式（例如MPEG 4 AAC）利用结构体来包含音频元数据。这些结构体称为<strong>magic cookies</strong>。使用音频队列服务以这种格式播放文件时，需要从音频文件中获取magic cookie，然后在开始播放之前应用到音频队列中。</p>
<p>清单3-14展示了如何从文件中获取magic cookie并将其应用到音频队列。你需要在开始播放之前调用该函数。</p>
<p><strong>清单3-14</strong> 为播放音频队列设置magic cookie</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UInt32</span> cookieSize = <span class="keyword">sizeof</span> (<span class="built_in">UInt32</span>);                   <span class="comment">// 1</span></span><br><span class="line"><span class="keyword">bool</span> couldNotGetProperty =                             <span class="comment">// 2</span></span><br><span class="line">    AudioFileGetPropertyInfo (                         <span class="comment">// 3</span></span><br><span class="line">        aqData.mAudioFile,                             <span class="comment">// 4</span></span><br><span class="line">        kAudioFilePropertyMagicCookieData,             <span class="comment">// 5</span></span><br><span class="line">        &amp;cookieSize,                                   <span class="comment">// 6</span></span><br><span class="line">        <span class="literal">NULL</span>                                           <span class="comment">// 7</span></span><br><span class="line">    );</span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> (!couldNotGetProperty &amp;&amp; cookieSize) &#123;              <span class="comment">// 8</span></span><br><span class="line">    <span class="keyword">char</span>* magicCookie =</span><br><span class="line">        (<span class="keyword">char</span> *) malloc (cookieSize);</span><br><span class="line"> </span><br><span class="line">    AudioFileGetProperty (                             <span class="comment">// 9</span></span><br><span class="line">        aqData.mAudioFile,                             <span class="comment">// 10</span></span><br><span class="line">        kAudioFilePropertyMagicCookieData,             <span class="comment">// 11</span></span><br><span class="line">        &amp;cookieSize,                                   <span class="comment">// 12</span></span><br><span class="line">        magicCookie                                    <span class="comment">// 13</span></span><br><span class="line">    );</span><br><span class="line"> </span><br><span class="line">    AudioQueueSetProperty (                            <span class="comment">// 14</span></span><br><span class="line">        aqData.mQueue,                                 <span class="comment">// 15</span></span><br><span class="line">        kAudioQueueProperty_MagicCookie,               <span class="comment">// 16</span></span><br><span class="line">        magicCookie,                                   <span class="comment">// 17</span></span><br><span class="line">        cookieSize                                     <span class="comment">// 18</span></span><br><span class="line">    );</span><br><span class="line"> </span><br><span class="line">    free (magicCookie);                                <span class="comment">// 19</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>设置magic cookie数据的预估大小。</li>
<li>接收<code>AudioFileGetPropertyInfo</code>函数的结果。如果成功，该函数返回<code>NoErr</code>，等同于布尔值<code>false</code>。</li>
<li><code>AudioFileGetPropertyInfo</code>函数（在<code>AudioFile.h</code>中声明），获取指定属性值的大小。可以用它来设置保存属性值的变量大小。</li>
<li>音频文件对象（类型为<code>AudioFileID</code>），表示要播放的音频文件。</li>
<li>表示音频文件的magic cookie数据的属性ID。</li>
<li>输入时，magic cookie数据的预估大小。输出时，是其实际大小。</li>
<li>用<code>NULL</code>表示不关心该属性的读/写访问权限。</li>
<li>如果音频文件确实包含magic cookie，则分配内存保存它。</li>
<li><code>AudioFileGetProperty</code>函数（在<code>AudioFile.h</code>中声明），获取指定属性的值。在这里，它将获取音频文件的magic cookie。</li>
<li>音频文件对象（类型为<code>AudioFileID</code>），表示要播放的以及要获取magic cookie的音频文件。</li>
<li>音频文件的magic cookie数据的属性ID。</li>
<li>输入时，<code>magicCookie</code>使用<code>AudioFileGetPropertyInfo</code>函数获得变量的大小。输出时，将是magic cookie的实际大小（以写入<code>magicCookie</code>变量的字节数为单位）。</li>
<li>输出时，音频文件的magic cookie。</li>
<li><code>AudioQueueSetProperty</code>函数给音频队列设置属性。在这里，它将给音频队列设置magic cookie，使其于要播放的音频文件中的magic cookie相匹配。</li>
<li>要为其设置magic cookie的音频队列。</li>
<li>音频队列的magic cookie的属性ID。</li>
<li>要播放文件中的magic cookie。</li>
<li>magic cookie的大小（以字节为单位）。</li>
<li>释放分配给magic cookie的内存。</li>
</ol>
<h2 id="分配和准备音频队列缓冲区">分配和准备音频队列缓冲区</h2>
<p>现在，请求之前创建的（参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW5">Create a Playback Audio Queue</a>）音频队列来准备一组音频队列缓冲区，如清单3-15所示。</p>
<p><strong>清单3-15</strong> 分配和准备音频队列缓冲区进行播放</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">aqData.mCurrentPacket = <span class="number">0</span>;                                <span class="comment">// 1</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; kNumberBuffers; ++i) &#123;                <span class="comment">// 2</span></span><br><span class="line">    AudioQueueAllocateBuffer (                            <span class="comment">// 3</span></span><br><span class="line">        aqData.mQueue,                                    <span class="comment">// 4</span></span><br><span class="line">        aqData.bufferByteSize,                            <span class="comment">// 5</span></span><br><span class="line">        &amp;aqData.mBuffers[i]                               <span class="comment">// 6</span></span><br><span class="line">    );</span><br><span class="line"> </span><br><span class="line">    HandleOutputBuffer (                                  <span class="comment">// 7</span></span><br><span class="line">        &amp;aqData,                                          <span class="comment">// 8</span></span><br><span class="line">        aqData.mQueue,                                    <span class="comment">// 9</span></span><br><span class="line">        aqData.mBuffers[i]                                <span class="comment">// 10</span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>数据包索引设为<code>0</code>，以便当前音频队列回调函数填充缓冲区（步骤7）时，是从音频文件的开头开始。</li>
<li>分配和准备一组音频队列缓冲区（<code>kNumberBuffers</code>设置为<code>3</code>，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW15">Define a Custom Structure to Manage State</a>）。</li>
<li><code>AudioQueueAllocateBuffer</code>函数通过为其分配内存来创建音频队列缓冲区。</li>
<li>分配缓冲区的音频队列。</li>
<li>新音频队列缓冲区大小（以字节为单位）。</li>
<li>输出时，把新的音频队列缓冲区添加到自定义结构体的<code>mBuffers</code>数组中。</li>
<li><code>HandleOutputBuffer</code>是播放音频队列的回调函数。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW2">Write a Playback Audio Queue Callback</a>。</li>
<li>音频队列的自定义结构体。</li>
<li>要调用其回调的音频队列。</li>
<li>要传递给音频队列回调的缓冲区。</li>
</ol>
<h2 id="设置音频队列播放增益">设置音频队列播放增益</h2>
<p>在音频队列开始播放之前，通过音频队列参数机制设置其增益，如清单3-16所示。有关参数机制的更多信息，可参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW15">Audio Queue Parameters</a>。</p>
<p><strong>清单3-16</strong> 设置音频队列的播放增益</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Float32 gain = <span class="number">1.0</span>;                                       <span class="comment">// 1</span></span><br><span class="line">    <span class="comment">// Optionally, allow user to override gain setting here</span></span><br><span class="line">AudioQueueSetParameter (                                  <span class="comment">// 2</span></span><br><span class="line">    aqData.mQueue,                                        <span class="comment">// 3</span></span><br><span class="line">    kAudioQueueParam_Volume,                              <span class="comment">// 4</span></span><br><span class="line">    gain                                                  <span class="comment">// 5</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>在<code>0</code>（静音）和<code>1</code>（单元增益）之间设置增益。</li>
<li><code>AudioQueueSetParameter</code>函数设置音频队列的参数值。</li>
<li>要设置参数的音频队列。</li>
<li>要设置的参数ID。<code>kAudioQueueParam_Volume</code>用于设置音频队列增益。</li>
<li>要应用于音频队列的增益设置。</li>
</ol>
<h2 id="启动和运行音频队列">启动和运行音频队列</h2>
<p>前面的代码已经为播放文件做了准备。下面是启动音频队列和维护run loop，如清单3-17所示。</p>
<p><strong>清单3-17</strong> 启动和运行音频队列</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">aqData.mIsRunning = <span class="literal">true</span>;                          <span class="comment">// 1</span></span><br><span class="line"> </span><br><span class="line">AudioQueueStart (                                  <span class="comment">// 2</span></span><br><span class="line">    aqData.mQueue,                                 <span class="comment">// 3</span></span><br><span class="line">    <span class="literal">NULL</span>                                           <span class="comment">// 4</span></span><br><span class="line">);</span><br><span class="line"> </span><br><span class="line"><span class="keyword">do</span> &#123;                                               <span class="comment">// 5</span></span><br><span class="line">    <span class="built_in">CFRunLoopRunInMode</span> (                           <span class="comment">// 6</span></span><br><span class="line">        kCFRunLoopDefaultMode,                     <span class="comment">// 7</span></span><br><span class="line">        <span class="number">0.25</span>,                                      <span class="comment">// 8</span></span><br><span class="line">        <span class="literal">false</span>                                      <span class="comment">// 9</span></span><br><span class="line">    );</span><br><span class="line">&#125; <span class="keyword">while</span> (aqData.mIsRunning);</span><br><span class="line"> </span><br><span class="line"><span class="built_in">CFRunLoopRunInMode</span> (                               <span class="comment">// 10</span></span><br><span class="line">    kCFRunLoopDefaultMode,</span><br><span class="line">    <span class="number">1</span>,</span><br><span class="line">    <span class="literal">false</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>设置自定义结构体标志，表示音频队列正在运行。</li>
<li><code>AudioQueueStart</code>函数在其自身的线程上启动音频队列。</li>
<li>要开始的音频队列。</li>
<li>用<code>NULL</code>表示因队列应立即开始播放。</li>
<li>定义轮询自定义结构体的<code>mIsRunning</code>字段，以检查音频队列是否已经停止。</li>
<li><code>CFRunLoopRunInMode</code>函数运行包含音频队列线程的run loop。</li>
<li>对run loop使用默认模式。</li>
<li>把run loop的运行时间设置为<code>0.25</code>秒。</li>
<li>用<code>false</code>表示run loop应在指定的时间内继续。</li>
<li>音频队列停止后，再运行一次run loop，以确保当前正在播放的音频队列缓冲区有足够时间完成。</li>
</ol>
<h2 id="播放后的清理">播放后的清理</h2>
<p>播放文件后，处理音频队列，关闭音频文件，并释放所有剩余资源，如清单3-18所示。</p>
<p><strong>清单3-18</strong> 播放音频文件后清理</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">AudioQueueDispose (                            <span class="comment">// 1</span></span><br><span class="line">    aqData.mQueue,                             <span class="comment">// 2</span></span><br><span class="line">    <span class="literal">true</span>                                       <span class="comment">// 3</span></span><br><span class="line">);</span><br><span class="line"> </span><br><span class="line">AudioFileClose (aqData.mAudioFile);            <span class="comment">// 4</span></span><br><span class="line"> </span><br><span class="line">free (aqData.mPacketDescs);                    <span class="comment">// 5</span></span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioQueueDispose</code>函数处理音频队列及其所有资源，包括缓冲区。</li>
<li>要处理的音频队列。</li>
<li>用<code>true</code>表示同步处理音频队列。</li>
<li>关闭播放的音频文件。<code>AudioFileClose</code>函数在<code>AudioFile.h</code>中声明。</li>
<li>释放用于保存数据包描述的内存。</li>
</ol>
<h2 id="总结">总结</h2>
<ul>
<li>使用AudioQueue实现播放功能，一般步骤：
<ol type="1">
<li>定义一个自定义结构体来管理状态、格式和路径信息。</li>
<li>编写音频队列回调函数来执行实际的播放。</li>
<li>编写代码以确定音频队列缓冲区的合适大小。</li>
<li>打开音频文件进行播放，然后确定其音频数据格式。</li>
<li>创建一个播放音频队列并进行相关配置。</li>
<li>分配和排队音频队列缓冲区。告诉音频队列开始播放。完成后，播放回调函数告诉音频队列停止。</li>
<li>处理音频队列，释放资源。</li>
</ol></li>
</ul>

    </div>

    
    
    
        

  <div class="followme">
    <p>欢迎关注我的其它发布渠道</p>

    <div class="social-list">

        <div class="social-item">
          <a target="_blank" class="social-link" href="/atom.xml">
            <span class="icon">
              <i class="fa fa-rss"></i>
            </span>

            <span class="label">RSS</span>
          </a>
        </div>
    </div>
  </div>


      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/Apple/" rel="tag"># Apple</a>
              <a href="/tags/Audio-Queue-Services-Programming-Guide/" rel="tag"># Audio Queue Services Programming Guide</a>
              <a href="/tags/%E9%9F%B3%E8%A7%86%E9%A2%91/" rel="tag"># 音视频</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/posts/audio_queue_services_pg_recording_audio/" rel="prev" title="Audio Queue Services Programming Guide：录制音频">
      <i class="fa fa-chevron-left"></i> Audio Queue Services Programming Guide：录制音频
    </a></div>
      <div class="post-nav-item">
    <a href="/posts/video_toolbox_compression_configuration/" rel="next" title="Video Toolbox压缩配置">
      Video Toolbox压缩配置 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    <div class="comments" id="gitalk-container"></div>

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AE%9A%E4%B9%89%E7%BB%93%E6%9E%84%E4%BD%93%E7%AE%A1%E7%90%86%E7%8A%B6%E6%80%81"><span class="nav-text">定义结构体管理状态</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%96%E5%86%99%E6%92%AD%E6%94%BE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0"><span class="nav-text">编写播放音频队列回调函数</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%92%AD%E6%94%BE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E5%9B%9E%E8%B0%83%E5%A3%B0%E6%98%8E"><span class="nav-text">播放音频队列回调声明</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BB%8E%E6%96%87%E4%BB%B6%E8%AF%BB%E5%8F%96%E5%88%B0%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA"><span class="nav-text">从文件读取到音频队列缓冲区</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%8E%92%E9%98%9F%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA"><span class="nav-text">排队音频队列缓冲区</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%81%9C%E6%AD%A2%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97"><span class="nav-text">停止音频队列</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AE%8C%E6%95%B4%E6%92%AD%E6%94%BE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0"><span class="nav-text">完整播放音频队列回调函数</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%96%E5%86%99%E5%87%BD%E6%95%B0%E8%AE%A1%E7%AE%97%E6%92%AD%E6%94%BE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA%E5%A4%A7%E5%B0%8F"><span class="nav-text">编写函数计算播放音频队列缓冲区大小</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%89%93%E5%BC%80%E9%9F%B3%E9%A2%91%E6%96%87%E4%BB%B6%E8%BF%9B%E8%A1%8C%E6%92%AD%E6%94%BE"><span class="nav-text">打开音频文件进行播放</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%8E%B7%E5%8F%96%E9%9F%B3%E9%A2%91%E6%96%87%E4%BB%B6%E7%9A%84cfurl%E5%AF%B9%E8%B1%A1"><span class="nav-text">获取音频文件的CFURL对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%89%93%E5%BC%80%E9%9F%B3%E9%A2%91%E6%96%87%E4%BB%B6"><span class="nav-text">打开音频文件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%8E%B7%E5%8F%96%E6%96%87%E4%BB%B6%E7%9A%84%E9%9F%B3%E9%A2%91%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F"><span class="nav-text">获取文件的音频数据格式</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%88%9B%E5%BB%BA%E6%92%AD%E6%94%BE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97"><span class="nav-text">创建播放音频队列</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%AE%BE%E7%BD%AE%E6%92%AD%E6%94%BE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E5%A4%A7%E5%B0%8F"><span class="nav-text">设置播放音频队列大小</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%AE%BE%E7%BD%AE%E7%BC%93%E5%86%B2%E5%8C%BA%E5%A4%A7%E5%B0%8F%E5%92%8C%E8%A6%81%E8%AF%BB%E5%8F%96%E7%9A%84%E6%95%B0%E6%8D%AE%E5%8C%85%E6%95%B0%E9%87%8F"><span class="nav-text">设置缓冲区大小和要读取的数据包数量</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BB%99%E6%95%B0%E6%8D%AE%E5%8C%85%E6%8F%8F%E8%BF%B0%E6%95%B0%E7%BB%84%E5%88%86%E9%85%8D%E5%86%85%E5%AD%98"><span class="nav-text">给数据包描述数组分配内存</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BB%99%E6%92%AD%E6%94%BE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E8%AE%BE%E7%BD%AEmagic-cookie"><span class="nav-text">给播放音频队列设置Magic Cookie</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%88%86%E9%85%8D%E5%92%8C%E5%87%86%E5%A4%87%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA"><span class="nav-text">分配和准备音频队列缓冲区</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%AE%BE%E7%BD%AE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E6%92%AD%E6%94%BE%E5%A2%9E%E7%9B%8A"><span class="nav-text">设置音频队列播放增益</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%90%AF%E5%8A%A8%E5%92%8C%E8%BF%90%E8%A1%8C%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97"><span class="nav-text">启动和运行音频队列</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%92%AD%E6%94%BE%E5%90%8E%E7%9A%84%E6%B8%85%E7%90%86"><span class="nav-text">播放后的清理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%80%BB%E7%BB%93"><span class="nav-text">总结</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="權咚"
      src="/images/icon.png">
  <p class="site-author-name" itemprop="name">權咚</p>
  <div class="site-description" itemprop="description">🤘🤯👾</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">84</span>
          <span class="site-state-item-name">文章</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">6</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">10</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/bqlin" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;bqlin" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="https://weibo.com/hearingdog" title="Weibo → https:&#x2F;&#x2F;weibo.com&#x2F;hearingdog" rel="noopener" target="_blank"><i class="fab fa-weibo fa-fw"></i>Weibo</a>
      </span>
  </div>


  <div class="links-of-blogroll motion-element">
    <div class="links-of-blogroll-title"><i class="fa fa-link fa-fw"></i>
      Links
    </div>
    <ul class="links-of-blogroll-list">
        <li class="links-of-blogroll-item">
          <a href="https://xiaozhuanlan.com/metal-reference-translation" title="https:&#x2F;&#x2F;xiaozhuanlan.com&#x2F;metal-reference-translation" rel="noopener" target="_blank">Metal内参</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://xiaozhuanlan.com/wwdc21" title="https:&#x2F;&#x2F;xiaozhuanlan.com&#x2F;wwdc21" rel="noopener" target="_blank">WWDC21 内参</a>
        </li>
    </ul>
  </div>

      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2016 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">權咚</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-chart-area"></i>
    </span>
    <span title="站点总字数">279k</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-coffee"></i>
    </span>
    <span title="站点阅读时长">4:14</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/pangu@4/dist/browser/pangu.min.js"></script>

<script src="/js/utils.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  
  <script>
    (function(){
      var canonicalURL, curProtocol;
      //Get the <link> tag
      var x=document.getElementsByTagName("link");
		//Find the last canonical URL
		if(x.length > 0){
			for (i=0;i<x.length;i++){
				if(x[i].rel.toLowerCase() == 'canonical' && x[i].href){
					canonicalURL=x[i].href;
				}
			}
		}
    //Get protocol
	    if (!canonicalURL){
	    	curProtocol = window.location.protocol.split(':')[0];
	    }
	    else{
	    	curProtocol = canonicalURL.split(':')[0];
	    }
      //Get current URL if the canonical URL does not exist
	    if (!canonicalURL) canonicalURL = window.location.href;
	    //Assign script content. Replace current URL with the canonical URL
      !function(){var e=/([http|https]:\/\/[a-zA-Z0-9\_\.]+\.baidu\.com)/gi,r=canonicalURL,t=document.referrer;if(!e.test(r)){var n=(String(curProtocol).toLowerCase() === 'https')?"https://sp0.baidu.com/9_Q4simg2RQJ8t7jm9iCKT-xh_/s.gif":"//api.share.baidu.com/s.gif";t?(n+="?r="+encodeURIComponent(document.referrer),r&&(n+="&l="+r)):r&&(n+="?l="+r);var i=new Image;i.src=n}}(window);})();
  </script>




  
<script src="/js/local-search.js"></script>











<script>
if (document.querySelectorAll('pre.mermaid').length) {
  NexT.utils.getScript('//cdn.jsdelivr.net/npm/mermaid@8/dist/mermaid.min.js', () => {
    mermaid.initialize({
      theme    : 'default',
      logLevel : 3,
      flowchart: { curve     : 'linear' },
      gantt    : { axisFormat: '%m/%d/%Y' },
      sequence : { actorMargin: 50 }
    });
  }, window.mermaid);
}
</script>


  

  

  

<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.css">

<script>
NexT.utils.loadComments(document.querySelector('#gitalk-container'), () => {
  NexT.utils.getScript('//cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js', () => {
    var gitalk = new Gitalk({
      clientID    : '7ed324dbfd2741c35c0e',
      clientSecret: '75a59e1f6a55202c4a1b0ec3570ad5b5dccbc390',
      repo        : 'bqlin.github.io',
      owner       : 'bqlin',
      admin       : ['bqlin'],
      id          : '2d3b8ac62b8b624080f5fd5a4fb6e8c3',
        language: 'zh-CN',
      distractionFreeMode: true
    });
    gitalk.render('gitalk-container');
  }, window.Gitalk);
});
</script>

<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"scale":1,"jsonPath":"/live2dw/assets/hijiki.model.json"},"display":{"position":"right","vOffset":-100},"mobile":{"show":false,"scale":0.5},"log":false});</script></body>
</html>
